#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>

#include "cola.h"
#include "termino_merge.h"
#include "utilidades.h"
#include "buffer_merge.h"

/** ***********									*********** **
 ** ***********	DEFINICION DEL TIPO DE DATOS	*********** **
 ** ***********									*********** **/

struct buffer{
	
	cola_t* buffer;
	unsigned long tamanio_maximo,tamanio_actual,terminos_procesados,cantidad_distinta_de_cero;
	FILE* destino;
	FILE* terminos_indice;
	FILE* terminos_string;
	
};

/*
 * Crea el buffer a partir del tamanio maximo recibido y el archivo
 * destino recibido. Lo devuelve luego de crearlo.
 */
buffer_t* buffer_crear(FILE* destino,unsigned long tamanio_maximo){
	
	if( !destino || (tamanio_maximo <= 0) ) return NULL;
	buffer_t* buffer_nuevo = (buffer_t*)malloc(sizeof(buffer_t));
	buffer_nuevo->tamanio_maximo = tamanio_maximo;
	buffer_nuevo->tamanio_actual = 0;
	buffer_nuevo->terminos_procesados = 0;
	buffer_nuevo->destino = destino;
	buffer_nuevo->cantidad_distinta_de_cero = 0;
	buffer_nuevo->buffer = cola_crear();
	
	FILE* terminos_indice = fopen("term_index.bin","wb");
	if (!terminos_indice )
		printf("ERROR: archivos de indice no creado\n");
	
	FILE* terminos_string = fopen("term_string","w");
	if (!terminos_string) 
		printf("ERROR: archivos de lexico no creado\n");

	if (!terminos_indice || !terminos_string) {
		free(buffer_nuevo);
		return NULL;
	}
	
	buffer_nuevo->terminos_indice = terminos_indice;
	buffer_nuevo->terminos_string = terminos_string;
	
	return buffer_nuevo;
	
}

/*
 * Aumenta la cantidad total de terminos procesados. Se le suma
 * la cantidad recibida a la cantidad actual. Devuelve la nueva
 * cantidad o cero si no recibio buffer.
 */
unsigned long buffer_aumentar_procesados(buffer_t* buffer,unsigned long cantidad_procesados){
	
	if( !buffer ) return 0;
	buffer->terminos_procesados += cantidad_procesados;
	return (buffer->terminos_procesados);
	
	
}

/*
 * Aumenta la cantidad de los valores distintos de cero en el merge.
 * Le suma la cantidad recibida al valor actual. Devuelve la nueva
 * cantidad o cero si no recibio buffer.
 */
unsigned long buffer_aumentar_distintos_de_cero(buffer_t* buffer,unsigned long cantidad){
	
	if( !buffer ) return 0;
	buffer->cantidad_distinta_de_cero += cantidad;
	return (buffer->cantidad_distinta_de_cero);
	
}

/*
 * Devuelve la cantidad de terminos procesados por el buffer.
 * Si no recibe buffer, devuelve cero.
 */
unsigned long buffer_obtener_procesados(buffer_t* buffer){
	
	if( !buffer ) return 0;
	return buffer->terminos_procesados;
	
}

/*
 * Devuelve la cantidad de valores distintos de cero procesados
 * por el buffer o cero si no recibe buffer.
 */
unsigned long buffer_obtener_distintos_de_cero(buffer_t* buffer){
	
	if( !buffer ) return 0;
	return buffer->cantidad_distinta_de_cero;
	
}

/*
 * Devuelve el tamanio actual del buffer o cero si no recibe buffer.
 */
unsigned long buffer_ver_tamanio(buffer_t* buffer){
	
	if( !buffer ) return 0;
	return (buffer->tamanio_actual);
	
}

/*
 * Devuelve el tamanio maximo del buffer o cero si no recibe buffer.
 */
unsigned long buffer_ver_tamanio_maximo(buffer_t* buffer){
	
	if( !buffer ) return 0;
	return (buffer->tamanio_maximo);
	
}

/*
 * Devuelve el puntero al archivo destino del buffer o NULL si no
 * recibe buffer.
 */
FILE* buffer_obtener_destino(buffer_t* buffer){
	
	if( !buffer ) return NULL;
	return (buffer->destino);
	
}

/*
 * Devuelve el archivo del indice de terminos del buffer.
 */
FILE* buffer_obtener_terminos_indice(buffer_t* buffer){
	
	if( !buffer ) return NULL;
	return (buffer->terminos_indice);
	
}

/*
 * Devuelve el archivo de los string de los terminos del buffer.
 */
FILE* buffer_obtener_terminos_string(buffer_t* buffer){
	
	if( !buffer ) return NULL;
	return (buffer->terminos_string);
	
}

/*
 * Devuelve true si la cola del buffer esta vacia o false en caso contrario.
 * Devuelve true si no recibe buffer.
 */
bool buffer_esta_vacio(buffer_t* buffer){
	
	if( !buffer ) return true;
	return (cola_esta_vacia(buffer->buffer));
	
}

/*
 * Devuelve el siguiente termino en la cola del buffer recibido.
 * Devuelve NULL si no recibe buffer.
 */
termino_merge_t* buffer_obtener_siguiente(buffer_t* buffer){
	
	if( !buffer ) return NULL;
	termino_merge_t* siguiente = (termino_merge_t*)cola_desencolar(buffer->buffer);
	return siguiente;
	
}

/*
 * Limpia el buffer recibido en su respectivo archivo destino. Si no
 * recibe buffer, o el buffer no tiene archivo destino especificado
 * o la cola del mismo esta vacia, no hace nada. Actualiza la cantidad
 * de terminos procesados y valores distintos de cero.
 */
void buffer_limpiar(buffer_t* buffer){
	
	if( !buffer || !buffer_obtener_destino(buffer) || buffer_esta_vacio(buffer) ) return;
	
	FILE* destino = buffer_obtener_destino(buffer);
	FILE* terminos_indice = buffer_obtener_terminos_indice(buffer);
	FILE* terminos_string = buffer_obtener_terminos_string(buffer);
	
	termino_merge_t* actual = NULL;
	
	unsigned long terminos_procesados = 0;
	unsigned long distinto_de_cero = 0;
	unsigned long int posicion_archivo_terminos = 0;
	
	while( !buffer_esta_vacio(buffer) ){
		
		actual = buffer_obtener_siguiente(buffer);
		
		distinto_de_cero += escribir_termino_merge_a_archivo(destino,actual);
		
		posicion_archivo_terminos = escribir_termino_a_indice(terminos_indice,terminos_string,actual,terminos_procesados,posicion_archivo_terminos);
		
		termino_merge_destruir(actual);
		terminos_procesados++;
		
	}
	
	buffer_aumentar_procesados(buffer,terminos_procesados);
	buffer_aumentar_distintos_de_cero(buffer,distinto_de_cero);
	
}

/*
 * Encola el termino recibido en el buffer recibido. Si no recibe buffer
 * o termino no realiza ninguna accion.
 */
void buffer_encolar(buffer_t* buffer,termino_merge_t* termino){
	
	if( !buffer || !termino ) return;
	cola_encolar(buffer->buffer,termino);
	
}

/*
 * Agrega el termino recibido al buffer recibido. Se fija si el termino
 * entra en el buffer, si no entra, limpia el buffer y lo agrega. Si entra,
 * simplemente lo agrega.
 */
void buffer_agregar_termino(buffer_t* buffer,termino_merge_t* termino){
	
	unsigned long suma_tamanios = buffer_ver_tamanio(buffer) + termino_merge_ver_tamanio(termino);
	
	if( suma_tamanios <= buffer_ver_tamanio_maximo(buffer) ) buffer_encolar(buffer,termino);
		
	else{ 
		
		buffer_limpiar(buffer);
		
		buffer_encolar(buffer,termino);
		
	}
		
}

/*
 * Destruye el buffer recibido por parametro. Si no recibe buffer,
 * no realiza ninguna accion.
 */
void buffer_destruir(buffer_t* buffer){
	
	if( !buffer ) return;
	buffer_limpiar(buffer);
	cola_destruir(buffer->buffer,NULL);
	fclose(buffer->terminos_indice);
	fclose(buffer->terminos_string);
	free(buffer);
	
}

